/** @file oal_mdev.c
  *
  * @brief This file contains the oal mdev function calls for oal
  * char dev communications.
  *
  *  Copyright (C) 2024, Artosyn Technologies. ALL RIGHTS RESERVED.
  *
  *  This Information is proprietary to Artosyn Technologies and MAY NOT
  *  be copied by any method or incorporated into another program without
  *  the express written consent of Artosyn. This Information or any portion
  *  thereof remains the property of Artosyn. The Information contained herein
  *  is believed to be accurate and Artosyn assumes no responsibility or
  *  liability for its use in any way and conveys no license or title under
  *  any patent or copyright and makes no representation or warranty that this
  *  Information is free from patent or copyright infringement.
  *
***/
/****************************
 * Include
 ****************************/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/vmalloc.h>
#include <linux/poll.h>
#include <linux/wait.h>
#include <linux/skbuff.h>
#include <linux/semaphore.h>

#include "utils/utils.h"
#include "control/ar_chardev.h"
#include "oal_main.h"
#include "oal_mdev.h"


/*****************************************************************************
 * typedef
 *****************************************************************************/


/***************************************************************************
 *declared
 ***************************************************************************/
static ar_chrdev_get_version_t drv_version = {.version = "0.1.0.1"};

/*****************************************************************************
 * global variables
 *****************************************************************************/

/*****************************************************************************
 * functions
 *****************************************************************************/

/**
 * @brief Get the version of the driver
 * (ioctl API AR_CHARDEV_IOCTL_GET_VERSION)
 *
 *  @param arg      ioctl arg
 *
 *  @return         0 for success, otherwise fail
 */
static int
oal_mdev_get_ar_net_version(unsigned long arg)
{
    if (copy_to_user((ar_chrdev_get_version_t*)arg, (void*)&drv_version, sizeof(ar_chrdev_get_version_t)))
    {
        return -EFAULT;
    }

    return 0;
}

/**
 *  @brief This function handles the wrapper_dev ioctl
 *
 *  @param m_dev    A pointer to wrapper_dev structure
 *  @param cmd      ioctl cmd
 *  @param arg      argument
 *
 *  @return         0 for success, otherwise fail.
 */
static int
oal_mdev_ioctl(struct m_dev *m_dev, unsigned int cmd, unsigned long arg)
{
    int ret = 0;
    oal_handle *handle = NULL;

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    if (!m_dev) {
        AR_DBG(DM_GENERIC, DL_ERR,"Ioctl for unknown device (m_dev=NULL)\n");
        ret = -ENODEV;
        goto done;
    }

    handle = (oal_handle*)m_dev->handle;
    if (!handle) {
        AR_DBG(DM_GENERIC, DL_ERR,"Ioctl for unknown handle (m_dev->handle=NULL)\n");
        ret = -ENODEV;
        goto done;
    }

    if (!test_bit(CHAR_DEV_RUNNING, &m_dev->flags)) {
        AR_DBG(DM_GENERIC, DL_ERR,"CHAR_DEV_RUNNING not set, flag=0x%lx\n",
               m_dev->flags);
        ret = -EBUSY;
        goto done;
    }

    AR_DBG(DM_GENERIC, DL_MSG, "mdev IOCTL: cmd=0x%x\n", cmd);

    switch (cmd)
    {
        case AR_CHARDEV_IOCTL_GET_VERSION:
            return oal_mdev_get_ar_net_version(arg);
        default:
            break;
    }

done:
    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
    return ret;
}

/**
 *  @brief This function closes the wrapper device
 *
 *  @param m_dev   A pointer to m_dev structure
 *
 *  @return        0 for success, otherwise fail.
 */
static int
oal_mdev_close(struct m_dev *m_dev)
{
    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    if (!test_and_clear_bit(CHAR_DEV_UP, &m_dev->flags)) {
        AR_DBG(DM_NET_DEV, DL_INF, AR_FN_LEAVE_STR);
        return 0;
    }
    /** wait up pending read and unregister char dev */
    wake_up_interruptible(&m_dev->req_wait_q);

    /* Drop queues */
    skb_queue_purge(&m_dev->rx_q);
    // skb_queue_purge(&m_dev->main_rx_q);

    if (!test_and_clear_bit(CHAR_DEV_RUNNING, &m_dev->flags)) {
        AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
        return 0;
    }

    module_put(THIS_MODULE);
    m_dev->flags = 0;

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);

    return 0;
}

/**
 *  @brief This function opens the wrapper device
 *
 *  @param m_dev   A pointer to m_dev structure
 *
 *  @return        0 for success, otherwise fail.
 */
static int
oal_mdev_open(struct m_dev *m_dev)
{
    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    if (try_module_get(THIS_MODULE) == 0) {
        mdev_req_unlock(m_dev);
        return OAL_STATUS_FAILURE;
    }

    set_bit(CHAR_DEV_RUNNING, &m_dev->flags);

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);

    return OAL_STATUS_SUCCESS;
}

/**
 *  @brief clean up m_devs
 *
 *  @param handle       A pointer to driver_handle
 *
 *  @return    N/A
 */
void
oal_mdev_clean_up(oal_handle *handle)
{
    struct m_dev *m_dev = NULL;

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    if (!handle) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s handle is NULL!\n", __FUNCTION__);
        return;
    }

    m_dev = &(handle->ar_drv_mdev);
    AR_DBG(DM_GENERIC, DL_MSG, "Delete %s\n", m_dev->name);

    if(mdev_req_lock(m_dev)) {
		return;
    }

    m_dev->close(m_dev);
    mdev_req_unlock(m_dev);

    /**  unregister m_dev to char_dev */
    if (handle->chardev_class) {
        ar_chardev_cleanup_one(m_dev, handle->chardev_class);
    }

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
    return;
}

/**
 *  @brief This function handles the device transmit
 *
 *  @param m_dev    A pointer to m_dev
 *  @param buf      A pointer to data buffer
 *  @param len      The length of the data buffer
 *
 *  @return    0 for success or failed otherwise.
 */
static int
oal_mdev_send(struct m_dev *m_dev, u8* buf, u16 len)
{
    int ret = 0;

    int retry_times = 10;
    oal_handle *handle;
    oal_status status;
    struct sk_buff *skb = NULL;

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    if (!m_dev) {
        AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
        return -ENODEV;
    }

    if (!test_bit(CHAR_DEV_RUNNING, &m_dev->flags)) {
        AR_DBG(DM_GENERIC, DL_ERR,"CHAR_DEV_RUNNING not set, flag=0x%lx\n",
               m_dev->flags);
        return -EBUSY;
    }

    handle = m_dev->handle;

    AR_DBG(DM_GENERIC, DL_VRB, "oal mdev(%s) send\n", m_dev->name);

    dump_bytes(DL_VRB, "chardev_write", buf, len);

    /* flag + bb_sock_opt_t */
    skb = dev_alloc_skb(len);
    if (!skb) {
        AR_DBG(DM_GENERIC, DL_WRN, "%s: Error! allocate skb failed!\n", __func__);
        mdev_req_unlock(m_dev);
        return -ENOMEM;
    }

    skb_put(skb, len);
    memcpy(skb->data, buf, len);

    dump_bytes(DL_VRB, "DAEMON TX", skb->data, skb->len);

    /*Send to usb directly*/
    while (retry_times > 0) {
        if (!handle || (handle->state != AR_BUS_STATE_UP)) {
            printk("handle is not inited!\n handle %p state:%d\n", handle, handle?handle->state:-1);
            status = OAL_STATUS_FAILURE;
            break;
        }

        status = oal_send_msg_req(handle, skb, false);
        if (status != OAL_STATUS_RESOURCE) {
            break;
        }
        else {
            retry_times--;
            udelay(10);
        }
    }

    if (status != OAL_STATUS_SUCCESS) {
        ret = -EIO;
        /** Free the skb if enqueue skb failed.  */
        dev_kfree_skb_any(skb);
        AR_DBG(DM_GENERIC, DL_WRN, "%s: tx failed, ret=%d\n", __func__, ret);
    }

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
    return ret;
}

/**
 *  @brief Init the oal mdev and register the process func
 *
 *  @param handle       A pointer to driver_handle
 *
 *  @return    0 for success or failed otherwise.
 */
int
oal_mdev_init(oal_handle *handle)
{
    int ret = 0;
    struct ar_cdev *char_dev = NULL;
    char dev_file[DEV_NAME_LEN + 5];
    char mod_name[AR_MODULE_NAME_LEN] = {0};

    if (!handle) {
        return -EINVAL;
    }

    /** Init the net_mdev */
    skb_queue_head_init(&handle->ar_drv_mdev.rx_q);
    init_waitqueue_head(&handle->ar_drv_mdev.req_wait_q);
    sema_init(&handle->ar_drv_mdev.req_lock, 1);
	spin_lock_init(&handle->ar_drv_mdev.lock);
    mutex_init(&handle->ar_drv_mdev.rlock);

    handle->ar_drv_mdev.open = oal_mdev_open;
    handle->ar_drv_mdev.send = oal_mdev_send;
    handle->ar_drv_mdev.close = oal_mdev_close;
    handle->ar_drv_mdev.ioctl = oal_mdev_ioctl;

    /** alloc char dev node */
    char_dev = ar_alloc_char_dev();
    if (!char_dev) {
        ret = -ENOMEM;
        goto done;
    }

    char_dev->minor = DRV_CHAR_MINOR_BASE + handle->handle_idx;
    char_dev->dev_type = CHARDEV_TYPE_DRV;

    snprintf(handle->ar_drv_mdev.name, sizeof(handle->ar_drv_mdev.name), "%s%d", OAL_MDEV_NAME_PREFIX, handle->handle_idx);
    snprintf(dev_file, sizeof(dev_file), "/dev/%s", handle->ar_drv_mdev.name);

    /** register m_dev to char device */
    handle->ar_drv_mdev.index = char_dev->minor;
    handle->ar_drv_mdev.handle = (void*)handle;
    char_dev->m_dev = &(handle->ar_drv_mdev);

    /** create char device node */
    sprintf(mod_name, "%s%d", AR_MODULE_NAME, handle->handle_idx);

    ar_register_char_dev(char_dev, handle->chardev_class, mod_name, handle->ar_drv_mdev.name);

#if defined(CDEV_SET_PERMISSION)
    /** chmod & chown for char device */
    ar_chardev_chmod(dev_file, 0666);
#endif

    AR_DBG(DM_GENERIC, DL_MSG, "Create drv chardev %s Done.\n", dev_file);

    return ret;

done:
    return ret;
}

/**
 *  @brief Deinit the oal mdev
 *
 *  @param handle       A pointer to driver_handle
 *
 *  @return N/A
 */
void
oal_mdev_deinit(oal_handle *handle)
{
    struct m_dev *m_dev = NULL;

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    if (!handle) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s handle is NULL!\n", __FUNCTION__);
        return;
    }

    m_dev = &(handle->ar_drv_mdev);

    AR_DBG(DM_GENERIC, DL_MSG, "Delete %s\n", m_dev->name);

    if (m_dev->close) {
        m_dev->close(m_dev);
    }

    /**  unregister m_dev to char_dev */
    if (handle->chardev_class) {
        ar_chardev_cleanup_one(m_dev, handle->chardev_class);
    }

    AR_DBG(DM_GENERIC, DL_MSG, "drv mdev removed done.\n");

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);

    return;
}